package com.smart.car.points.aspect;

import cn.hutool.core.date.DateUtil;
import com.smart.car.common.res.bean.ResponseResult;
import com.smart.car.common.res.exception.BusinessException;
import com.smart.car.common.res.utils.IpByWebUtil;
import com.smart.car.points.annotation.RequestLimit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 2.AOP切面，对前台接口防刷，进行次数限制
 * (配合自定义注解使用)
 *
 * @author zoutao.blog.csdn.net
 * @date 2021/5/28
 */
@Slf4j
@Aspect
@Component
public class ReqLimitAop {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //Pointcut是指定通用切入点表达式
    @Pointcut("execution(public * com.smart.car.points.controller.*.*(..))")
    public void WebPointCut() {
    }

    /**
     * Before用于配置当前方法是一个前置通知
     * JoinPoint对象封装了SpringAop中切面方法的信息,
     * 在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
     */
    @Before("WebPointCut() && @annotation(limit)")
    public ResponseResult ifovertimes(final JoinPoint joinPoint, RequestLimit limit) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (null == attributes) {
                return ResponseResult.fail("非法请求");
            }
            HttpServletRequest request = attributes.getRequest();

            //1.获取ip
            String ip = IpByWebUtil.getIpAddr(request);
            String ip2 = request.getRemoteAddr();
            String url = request.getRequestURL().toString();

            //2.前缀+ip构成key
            String key = "points_req_limit:" + ip;
            //3.检查key是否存在，返回boolean值
            boolean flag = redisTemplate.hasKey(key);
            if (flag) {
                // 调用次数+1
                redisTemplate.boundValueOps(key).increment(1);
                // 设置过期时间
                redisTemplate.expire(ip, limit.time(), TimeUnit.SECONDS);
                String sumVal = redisTemplate.opsForValue().get(key);
                int sum = Integer.valueOf(sumVal);
                // 次数限制
                if (sum > limit.count()) {
                    log.info("用户[" + ip + "]，访问[" + url + "]超过了限定的次数[" + limit.count() + "]");
                    //throw new BusinessException("第" + sum + "次请求，请求失败！一分钟内不能再次请求！");
                    return ResponseResult.fail("请求失败！请求不可以重复提交！");
                } else {
                    System.out.println("第" + sum + "次请求，请求成功！");
                }
            } else {
                // 第一次调用或key过期，则将value（调用次数）设置为1
                redisTemplate.opsForValue().set(key, "1", limit.time(), TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            log.error("接口防刷的AOP类错误，请检查是否有非法盗刷行为。触发时间：{}", DateUtil.now());
            e.printStackTrace();
        }
        return ResponseResult.fail("请求失败！请求不可以重复提交！");
    }
}
